package com.wzw.expression.expression.parser;

import com.wzw.expression.expression.operation.Operation;
import com.wzw.expression.expression.operation.AnnotationOperationProvider;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Wangzhiwen
 */
public class SpringAnnotationParser implements AnnotationParser {

    private static final Map<Class<? extends Annotation>, AnnotationOperationProvider> PROVIDER_MAP = new ConcurrentHashMap<>(4);

    public void addProvider(AnnotationOperationProvider provider) {
        PROVIDER_MAP.put(provider.getType(), provider);
    }

    @Override
    public boolean isCandidateClass(Class<?> targetClass) {
        return AnnotationUtils.isCandidateClass(targetClass, PROVIDER_MAP.keySet());
    }

    @Override
    public Collection<Operation> parseAnnotations(Class<?> type) {
        return internalParseAnnotations(type);
    }

    @Override
    public Collection<Operation> parseAnnotations(Method method) {
        return internalParseAnnotations(method);
    }

    @Nullable
    private Collection<Operation> internalParseAnnotations(AnnotatedElement ae) {
        Collection<Operation> ops = parseAnnotations(ae, false);
        if (ops != null && ops.size() > 1) {
            // More than one operation found -> local declarations override interface-declared ones...
            Collection<Operation> localOps = parseAnnotations(ae, true);
            if (localOps != null) {
                return localOps;
            }
        }
        return ops;
    }

    @Nullable
    private Collection<Operation> parseAnnotations(AnnotatedElement ae, boolean localOnly) {
        Collection<? extends Annotation> annotations =
                (
                        localOnly ?
                                AnnotatedElementUtils.getAllMergedAnnotations(ae, PROVIDER_MAP.keySet()) :
                                AnnotatedElementUtils.findAllMergedAnnotations(ae, PROVIDER_MAP.keySet())
                );
        if (annotations.isEmpty()) {
            return null;
        }
        final Collection<Operation> ops = new ArrayList<>(1);
        annotations.forEach(an -> PROVIDER_MAP.values().stream()
                .filter(v -> v.getType().isAssignableFrom(an.getClass()))
                .forEach(v -> ops.addAll(v.get(an, ae))));
        return ops;
    }
}
